home *** CD-ROM | disk | FTP | other *** search
- /*****
- *
- * ProcessUtil.c
- *
- * This is a support file for "Grant's CGI Framework".
- * Please see the license agreement that accompanies the distribution package
- * for licensing details.
- *
- * Copyright ©1995,1996 by Grant Neufeld
- * grant@acm.com
- * http://arpp.carleton.ca/grant/
- *
- *****/
-
- #include "MyConfiguration.h"
-
- #include <Threads.h>
-
- #include "compiler_stuff.h"
- #include "globals.h"
-
- #include "DebugUtil.h"
-
- #include "ProcessUtil.h"
-
-
- /*** LOCAL VARIABLES ***/
-
- static short vAppBusyLevel; /* how busy the application is */
- static UInt32 vSleepTicksDefault; /* sleep time when app is idle */
- static UInt32 vSleepTicksBusy; /* sleep time when app is processing */
-
- static ThreadID vThreadMain; /* main application thread id */
- static Boolean vThreadMainIsAsleep;
-
- #if kCompileWithDeferredTask
- static ThreadID vDeferredTaskThread;
- #endif
- //static ThreadID vThreadStatusUpdate;
-
- /* an array of sleeping thread IDs */
- #if kStartupThreadsPreallocate > nil
- static ThreadID vThreadSleepers[kStartupThreadsPreallocate]; /* array of sleepers */
- static ThreadID vThreadSleeperHead; /* next thread to wake up */
- static ThreadID vThreadSleeperAddHere; /* next open spot in queue */
- static short vThreadSleepersTotal;
- #endif
-
-
- /*** LOCAL PROTOTYPES ***/
-
- pascal void threadTermination ( ThreadID, void * );
- pascal void * threadDeferredTaskThread ( void * );
- static void threadSleepersInit ( void );
-
-
- /*** FUNCTIONS ***/
-
- /* */
- void
- ProcessStartup ( void )
- {
- ProcessSleepSetDefault ( kSleepTicks );
- ProcessSleepSetBusy ( kSleepTicksWhenBusy );
- ProcessSleepDefault ();
-
- /* we're not busy yet */
- vAppBusyLevel = nil;
-
- gFrontProcess = ProcessCurrentIsFront ();
-
- #if kCompileWithProcessFileSpec
- ProcessGetMyFSSpec ( &gProcessFSSpec );
- #endif
- } /* ProcessStartup */
-
-
- /* Give time for other processes (both internal to the app and other applications) */
- p_export
- void
- ProcessGiveTime ( UInt32 sleepTicks )
- {
- OSErr theErr;
- #if kCompileWithThreadsOptional
- EventRecord theEvent;
-
- if ( gHasThreadMgr )
- {
- #endif
-
- theErr = ThreadYield ( nil, true );
-
- #if kCompileWithThreadsOptional
- }
- else
- {
- if ( sleepTicks > nil )
- {
- /* if sleepTicks is a 'usable' value, use it */
- WaitNextEvent ( nil, &theEvent, sleepTicks, NULL );
- }
- else
- {
- /* otherwise, use the current application sleep ticks setting */
- WaitNextEvent ( nil, &theEvent, gSleepTicks, NULL );
- }
- }
- #endif
- } /* ProcessGiveTime */
-
-
- /* Determine if the current application is active (is in front of all others) */
- Boolean
- ProcessCurrentIsFront ( void )
- {
- #if kCompileWithForeground
-
- Boolean isFront;
- ProcessSerialNumber myPSN;
- OSErr theErr;
-
- /* get my process serial number. IM:Processes 2-21 */
- theErr = GetCurrentProcess ( &myPSN );
- if ( theErr != noErr )
- {
- /* might want to do something else here to handle the error */
- return false;
- }
-
- isFront = ProcessIsFront ( &myPSN );
-
- return isFront;
-
- #else /* if not kCompileWithForeground */
-
- return false;
-
- #endif /* kCompileWithForeground */
- } /* ProcessCurrentIsFront */
-
-
- /* Run through WaitNextEvent, not handling events, until the process comes
- to the foreground.
- Returns false if timeout is reached before process brought to front.
- Timeout is in ticks (60ths of a second). If timeout is nil - doesn't timeout. */
- Boolean
- ProcessWaitUntilFront ( unsigned long timeout )
- {
- #if kCompileWithForeground
- Boolean isFront;
- long endTime;
- long sleepTicks;
-
- isFront = ProcessCurrentIsFront ();
- if ( !isFront )
- {
- endTime = TickCount() + timeout;
-
- if ( (timeout > 20) || (timeout == nil) )
- {
- sleepTicks = 20;
- }
- else
- {
- sleepTicks = timeout;
- }
-
- do
- {
- ProcessGiveTime ( sleepTicks );
-
- isFront = ProcessCurrentIsFront ();
- } while ( !isFront && ((timeout == nil) || (endTime >= TickCount())) );
- }
-
- return isFront;
-
- #else
- /* can't be in foreground/front if application is background only */
- return false;
- #endif /* kCompileWithForeground */
- } /* ProcessWaitUntilFront */
-
-
- /* Determine if the specified application is active (is in front of all others) */
- Boolean
- ProcessIsFront ( ProcessSerialNumber *thePSN )
- {
- #if kCompileWithForeground
- OSErr theErr;
- ProcessSerialNumber frontPSN;
-
- my_assert ( thePSN != NULL, "\pProcessIsFront: thePSN ptr is NULL" );
-
- theErr = GetFrontProcess ( &frontPSN );
- if ( theErr != noErr )
- {
- /* might want to do something else here to handle the error */
- return false;
- }
-
- return ( (frontPSN.lowLongOfPSN == thePSN->lowLongOfPSN) &&
- (frontPSN.highLongOfPSN == thePSN->highLongOfPSN) );
-
- #else
- /* can't be in foreground/front if application is background only */
- return false;
- #endif /* kCompileWithForeground */
- } /* ProcessIsFront */
-
-
- /* Fill out an already allocated FSSpec with the current process' location.
- Thanks to Gregory S. Combs for providing most of this function. */
- OSErr
- ProcessGetMyFSSpec ( FSSpec *procFileSpec )
- {
- OSErr theErr;
- ProcessSerialNumber curPSN;
- ProcessInfoRec procInfo;
- Str255 appName;
-
- my_assert ( procFileSpec != NULL, "\pProcessGetMyFSSpec: procFileSpec ptr is NULL" );
-
- theErr = GetCurrentProcess ( &curPSN );
- if ( theErr == noErr )
- {
- procInfo.processInfoLength = sizeof ( ProcessInfoRec );
- procInfo.processName = appName;
- procInfo.processAppSpec = procFileSpec;
-
- theErr = GetProcessInformation ( &curPSN, &procInfo );
- }
-
- return theErr;
- } /* ProcessGetMyFSSpec */
-
-
- /** BUSY LEVEL and SLEEP TICKS **/
- #pragma mark -
-
- /* The 'busy' level of the application affects how much time it will try to
- grab for processing */
-
- /* */
- p_export
- void
- ProcessIsMoreBusy ( void )
- {
- OSErr theErr;
-
- if ( vAppBusyLevel == nil )
- {
- /* we weren't busy, so need to move into busy state */
- /* adjust the sleepTicks */
- ProcessSleepBusy ();
- /* put the deferredTasks thread to sleep */
- #if kCompileWithDeferredTask
- if ( vDeferredTaskThread != nil )
- {
- theErr = SetThreadState ( vDeferredTaskThread, kStoppedThreadState, nil );
- }
- #endif
- }
- vAppBusyLevel++;
- } /* ProcessIsMoreBusy */
-
- /* */
- p_export
- void
- ProcessIsLessBusy ( void )
- {
- OSErr theErr;
-
- vAppBusyLevel--;
- if ( vAppBusyLevel == nil )
- {
- /* we're no longer busy, so need to move into non-busy state */
- /* adjust the sleepTicks */
- ProcessSleepDefault ();
- /* wake up the deferredTasks thread */
- #if kCompileWithDeferredTask
- if ( vDeferredTaskThread != nil )
- {
- theErr = SetThreadState ( vDeferredTaskThread, kReadyThreadState, nil );
- }
- #endif
- }
- } /* ProcessIsLessBusy */
-
-
- /* sleepTicks is used to determine how much time to give for other processes
- to use. */
-
- /* set the number of ticks used for normal sleep time */
- p_export
- void
- ProcessSleepSetDefault ( UInt32 sleepTicks )
- {
- vSleepTicksDefault = sleepTicks;
- } /* ProcessSleepSetDefault */
-
- /* set the number of ticks used for busy sleep time */
- p_export
- void
- ProcessSleepSetBusy ( UInt32 sleepTicks )
- {
- vSleepTicksBusy = sleepTicks;
- } /* ProcessSleepSetBusy */
-
- /* Call this when app becmoes not busy */
- p_export
- void
- ProcessSleepDefault ( void )
- {
- gSleepTicks = vSleepTicksDefault;
- } /* ProcessSleepDefault */
-
- /* Call this when app becmoes busy */
- p_export
- void
- ProcessSleepBusy ( void )
- {
- gSleepTicks = vSleepTicksBusy;
- } /* ProcessSleepBusy */
-
-
- /** THREAD FUNCTIONS **/
- #pragma mark -
-
- void
- ThreadStartup ( void )
- {
- OSErr theErr;
- long feature;
-
- #if kCompileWithThreadsOptional
- gHasThreadMgr = false;
- #endif
-
- theErr = Gestalt ( gestaltThreadMgrAttr, &feature );
- if ( theErr == noErr )
- {
- if (
- #ifdef powerc
- ( feature & (1 << gestaltThreadsLibraryPresent) ) &&
- /* ••• Can anybody who does PowerPC code tell me why the following
- line doesn't work?
- ( (Ptr)NewThread != kUnresolvedSymbolAddress ) &&
- ••• */
- #endif /* def powerc */
- /* the feature check applies to both 68K & PPC */
- ( feature & (1 << gestaltThreadMgrPresent) ) )
- {
- #if kCompileWithThreadsOptional
- gHasThreadMgr = true;
- #endif
-
- gThreadQuit = false;
-
- /* no sub threads running yet */
- // #if kStartupThreadsPreallocate > nil
- gThreadTotal = nil;
- // /* no sleeping thread yet */
- // gThreadSleeperIndex = 0;
- // gThreadSleeper = nil;
- // #endif
-
- /* determine the id of the main (current) thread */
- GetCurrentThread ( &vThreadMain );
- vThreadMainIsAsleep = false;
-
- #if (kStartupThreadsPreallocate > nil)
- /* pre-allocate the required number of threads */
- CreateThreadPool ( kCooperativeThread, kStartupThreadsPreallocate, nil );
-
- threadSleepersInit ();
- #endif
-
- /* create the deferred tasks thread */
- #if kCompileWithDeferredTask
- theErr = ThreadNewThreadFromPool ( threadDeferredTaskThread,
- (void *)NULL, (void**)NULL, &vDeferredTaskThread );
- #endif
- }
- }
- } /* ThreadStartup */
-
-
- /* you should always make sure that your sub-threads can't get 'stuck' in a loop.
- They should, at the very least, check gThreadQuit periodically so that the
- application can quit without getting hung on threads that won't go away. */
-
-
- /* Allocate a new thread from the existing pool of threads.
- If there are no threads available, yield to other threads until one finishes.
- The Thread Manager must be available for this function to work. */
- #if kStartupThreadsPreallocate > nil
- p_export
- OSErr
- ThreadNewThreadFromPool (
- ThreadEntryProcPtr threadEntry,
- void * threadParam,
- void ** threadResult,
- ThreadID * threadMade )
- {
- OSErr theErr;
- short threadsFree;
- ThreadID currentThread;
-
- #if kCompileWithThreadsOptional
- my_assert ( gHasThreadMgr, "\pThreadNewThreadFromPool: Thread Manager not available" );
- #endif
-
- theErr = GetFreeThreadCount ( kCooperativeThread, &threadsFree );
- if ( theErr == noErr )
- {
- theErr = GetCurrentThread ( ¤tThread );
- }
-
- if ( (theErr == noErr) && (threadsFree == nil) )
- {
- /* Put the current thread to sleep, to be woken up when a thread
- becomes available. */
- theErr = ThreadSleep ( currentThread );
- }
-
- if ( theErr == noErr )
- {
- /* Install the new thread using a premade thread from the pool. */
- theErr = NewThread ( kCooperativeThread, threadEntry, threadParam, nil,
- kFPUNotNeeded + kUsePremadeThread, threadResult, threadMade );
- }
-
- if ( theErr == noErr )
- {
- /* set the termination function for the thread. */
- SetThreadTerminator ( *threadMade, threadTermination, NULL );
-
- /* increment the total number of sub-threads. */
- ++gThreadTotal;
- }
-
- return theErr;
- } /* ThreadNewThreadFromPool */
- #endif
-
-
- /* The Thread Manager must be available for this function to work. */
- #if kStartupThreadsPreallocate > nil
- pascal void
- threadTermination ( ThreadID threadTerminated, void *terminationProcParam )
- {
- OSErr theErr;
-
- #if kCompileWithThreadsOptional
- my_assert ( gHasThreadMgr, "\pmyThreadTermination: Thread Manager not available" );
- #endif
- my_assert ( gThreadTotal > nil, "\pmyThreadTermination: gThreadTotal is too small" );
-
- /* if there are sleeping threads, wake one up since this one will be disposed */
- theErr = ThreadWakeNext ();
-
- /* Lower the count of sub-threads. */
- --gThreadTotal;
- } /* threadTermination */
- #endif
-
-
- /* Call this instead of YieldToAnyThread.
- suggestThread is the suggested (but not required) thread to yield to next. */
- p_export
- OSErr
- ThreadYield ( ThreadID suggestThread, Boolean useWNE )
- {
- OSErr theErr;
- #if kStartupThreadsPreallocate > nil
- EventRecord theEvent;
- #endif
-
- #if kCompileWithThreadsOptional
- my_assert ( gHasThreadMgr, "\pThreadYield: Thread Manager not available" );
- #endif
-
- theErr = YieldToAnyThread ();
-
- /* if there's a sleeping thread, it's probably the main thread, so we'll
- need to give time to other apps to process */
- #if kStartupThreadsPreallocate > nil
- if ( useWNE && (vThreadSleepersTotal > nil) )
- {
- WaitNextEvent ( nil, &theEvent, gSleepTicks, NULL );
- }
- #endif
-
- return theErr;
- } /* ThreadYield */
-
-
- /* Give all sub-threads a chance to finish */
- #if (kStartupThreadsPreallocate > nil)
- void
- ThreadFinishAllSubThreads ( void )
- {
- OSErr theErr;
- ThreadID currentThread;
-
- #if kCompileWithThreadsOptional
- my_assert ( gHasThreadMgr, "\pThreadFinishAllSubThreads: Thread Manager not available" );
- #endif
-
- theErr = GetCurrentThread ( ¤tThread );
- if ( currentThread != vThreadMain )
- {
- /* lower the count of sub-threads to prevent this sub-thread from
- preventing the exit of the while loop below */
- --gThreadTotal;
- }
-
- gThreadQuit = true;
-
- while ( gThreadTotal > nil )
- {
- if ( gThreadTotal <= vThreadSleepersTotal )
- {
- /* remaining threads are sleeping, so wake one up */
- ThreadWakeNext ();
- }
-
- /* while there remain other threads (not including the current and main),
- let them finish before quitting */
- ProcessGiveTime ( nil );
- }
-
- if ( currentThread != vThreadMain )
- {
- /* increase the count of sub-threads */
- ++gThreadTotal;
- }
- } /* ThreadFinishAllSubThreads */
- #endif
-
-
- /** Deferred Tasks (low-priority) thread **/
- #pragma mark -
- #if kCompileWithDeferredTask
-
- /* */
- pascal void *
- threadDeferredTaskThread ( void *threadParam )
- {
- ThreadID currentThread;
-
- #if kCompileWithThreadsOptional
- my_assert ( gHasThreadMgr, "\pthreadDeferredTaskThread: Thread Manager not available" );
- #endif
-
- /* continually call CustomDeferredTask until it is time to quit */
- while ( !gThreadQuit )
- {
- CustomDeferredTask ();
- ProcessGiveTime ( nil );
- }
-
- /* Find the ID of current thread and use DisposeThread to dispose of it so
- that my custom thread termination procedure will be used to recycle
- this thread's allocation for the thread pool. */
- GetCurrentThread ( ¤tThread );
- DisposeThread ( currentThread, (void *)noErr, true );
-
- /* This line below is actually irrelevant, since the DisposeThread call above
- will result in the immediate termination of this thread.
- I keep it in because a return result is needed for the compiler not to
- issue a warning (and I have the "treat all warnings as errors" flag set
- in my compiler, like every programmer should). */
- return (void *)noErr;
- } /* threadDeferredTaskThread */
-
- #endif /* kCompileWithDeferredTask */
-
-
- /** SLEEPING THREADS **/
- #pragma mark -
- #if kStartupThreadsPreallocate > nil
- /* this section takes care of dealing with putting threads to sleep, waking
- them up, and keeping track of the sleeping threads.
- It only really applies to situations when resources are tied up and a
- thread can't proceed until some resources become available. In particular,
- when allocating new threads from the pool of preallocated threads, if the
- pool contains no unused threads, the thread attempting to allocate can go
- to sleep until some other thread quits resulting in a thread becoming
- available.
- If you want to be able to put a specific thread to sleep and later wake it
- up under specific circumstances (where you don't want to rely on the queue
- to wake it up because it might be woken too early or too late), you will
- have to handle the sleeping and waking on your own (don't touch the
- sleeping threads queue implemented in this module.
- I've implemented a queue (vThreadSleepers) to track the sleeping threads;
- it stores the ThreadIDs which can then be used to wake up the threads on
- a FIFO (first in first out) basis.
- The position for where to add threads to the queue (as they are put to
- sleep) is tracked in vThreadSleeperAddHere.
- The position for where the next thread to wake is in the queue is
- tracked in vThreadSleeperHead.
- The first item in the vThreadSleepers array is identified as 0 (zero),
- and the last is identified by kStartupThreadsPreallocate - 1. */
-
- /* */
- static void
- threadSleepersInit ( void )
- {
- short counter;
-
- #if kCompileWithThreadsOptional
- my_assert ( gHasThreadMgr, "\pthreadSleepersInit: Thread Manager not available" );
- #endif
-
- for ( counter = 0; counter < kStartupThreadsPreallocate; counter++ )
- {
- vThreadSleepers[counter] = nil;
- }
- vThreadSleeperHead = nil;
- vThreadSleeperAddHere = nil;
- vThreadSleepersTotal = nil;
- } /* threadSleepersInit */
-
-
- /* */
- p_export OSErr
- ThreadSleep ( ThreadID theThread )
- {
- OSErr theErr;
-
- #if kCompileWithThreadsOptional
- my_assert ( gHasThreadMgr, "\pThreadSleep: Thread Manager not available" );
- #endif
- my_assert ( theThread != nil, "\pThreadSleep: theThread is nil" );
-
- if ( vThreadSleepersTotal == kStartupThreadsPreallocate )
- {
- /* cannot put all of the application threads to sleep, at least one
- must be running! */
- theErr = true; /* ••• this should be a more meaningful error value */
- }
- else
- {
- my_assert ( vThreadSleepers[vThreadSleeperAddHere] == nil,
- "\pThreadSleep: the AddHere spot in the vThreadSleepers queue is not nil" );
-
- if ( theThread == vThreadMain )
- {
- /* we're putting the main thread to sleep */
- vThreadMainIsAsleep = true;
- }
-
- /* add the threadID to the queue */
- vThreadSleepers[vThreadSleeperAddHere] = theThread;
- /* adjust the index to the next open spot in the queue */
- ++vThreadSleeperAddHere;
- if ( vThreadSleeperAddHere >= kStartupThreadsPreallocate )
- {
- vThreadSleeperAddHere = 0;
- }
- /* we've got one more sleeping thread, now */
- ++vThreadSleepersTotal;
-
- /* this is the call to put the thread to sleep */
- theErr = SetThreadState ( theThread, kStoppedThreadState, nil );
- }
-
- return theErr;
- } /* ThreadSleep */
-
-
- /* Waking up a thread does not mean that it will immediately start processing,
- it just means that the thread will be back in the set of threads that can
- be yielded to when ThreadYield is called. */
- p_export
- OSErr
- ThreadWakeNext ( void )
- {
- OSErr theErr;
- ThreadID theThread;
-
- #if kCompileWithThreadsOptional
- my_assert ( gHasThreadMgr, "\pThreadWakeNext: Thread Manager not available" );
- #endif
-
- /* extract the threadID from the queue */
- theThread = vThreadSleepers[vThreadSleeperHead];
-
- if ( theThread == nil )
- {
- /* no sleeping threads */
- theErr = true; /* ••• need better error value here */
- }
- else
- {
- if ( theThread == vThreadMain )
- {
- /* we're waking up the main thread */
- vThreadMainIsAsleep = false;
- }
-
- /* adjust the index of the head of the queue to the next in line */
- vThreadSleepers[vThreadSleeperHead] = nil;
- ++vThreadSleeperHead;
- if ( vThreadSleeperHead >= kStartupThreadsPreallocate )
- {
- vThreadSleeperHead = nil;
- }
- /* we've got one less sleeping thread, now */
- --vThreadSleepersTotal;
-
- /* this is the call to wake up the thread */
- theErr = SetThreadState ( theThread, kReadyThreadState, nil );
- }
-
- return theErr;
- } /* ThreadWakeNext */
-
-
- /* */
- void
- ThreadWakeAll ( ThreadID theThread )
- {
- #if kCompileWithThreadsOptional
- my_assert ( gHasThreadMgr, "\pThreadWakeAll: Thread Manager not available" );
- #endif
-
- while ( vThreadSleepersTotal > nil )
- {
- ThreadWakeNext ();
- }
- } /* ThreadWakeAll */
-
- #endif /* kStartupThreadsPreallocate > nil */
-
-
- /***** EOF *****/
-